/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package yuhuibear.poat.query;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

import yuhuibear.poat.exception.NotMappedType;
import yuhuibear.poat.object.bean.PersistentClassLoader;
import yuhuibear.poat.util.NameConvertor;

/**
 * 构建sql 语句的辅助对象。<br>
 * 所有的 from 内容会自动的在 sql 语句的其他部分被解析出来， 并添加到 form 中。
 * <ul>
 * <li>使用 select 模式时，如果用到as , 则 as 中涉及到的表会自动的加入到 from 中。</li>
 * <li>使用 update 模式时， 如果设置了属性Map 则可以使用条件更新。</li>
 * </ul>
 * 
 * @author yuhuibear
 */
public class ObjectQuery extends Query {

	private boolean distinct = true;

	private int type = -1;

	private final Pattern order = Pattern.compile(".*order\\sby\\s.*", Pattern.CASE_INSENSITIVE);

	private final Pattern group = Pattern.compile(".*group\\sby\\s.*", Pattern.CASE_INSENSITIVE);

	/**
	 * 要操作的关系表
	 */
	private String targetRelation;

	private Class<?> targetType;

	private List<String> attrSet;

	private List<String> fromSet;

	private List<String> whereSet;

	private List<String> asSet;

	private Integer limit;

	private Integer offset;

	private String orderBy = null;

	private String groupBy = null;

	private PersistentClassLoader ba;

	/**
	 * 创建查询语句对象。
	 * 
	 * @param type
	 *            只能是INSERT, SELECT, UPDATE, DEL四种类型.
	 */
	public ObjectQuery(PersistentClassLoader analyser, int type) {
		ba = analyser;
		if (type >= CMD.length || type < 0) {
			this.type = 1;
		}
		attrSet = new ArrayList<String>();
		fromSet = new ArrayList<String>();
		whereSet = new ArrayList<String>();
		asSet = new ArrayList<String>();
		this.type = type;
	}

	/**
	 * 设置查询返回的对象类型。
	 * 
	 * @param type
	 * @param distinct
	 */
	public void setTarget(Class<?> type, boolean distinct) throws Exception {
		setTarget(type);
		this.distinct = distinct;
	}

	/**
	 * 设置查询返回的对象类型。
	 * 
	 * @param type
	 */
	public void setTarget(Class<?> type) throws Exception {
		if (type != null && !ba.isDBEntity(type)) { throw new NotMappedType(type); }
		this.targetType = type;
		targetRelation = ba.analyse(type).getDBName();
		this.distinct = true;
	}

	/**
	 * 用于设置对象的属性。
	 * 
	 * @param atrb
	 * @throws Exception
	 */
	public void setUpdateAttributes(String atrb) throws Exception {
		String atr = ba.convertAttributeExpression(atrb);
		if (atr != null && atr.length() > 0)
			attrSet.add(atr);
	}

	/**
	 * 原名字可以是对象的一个属性， 也可以是函数，函数以小写开头。<br>
	 * 
	 * <hl>不能和setTarget 同时使用.</hl>
	 * 
	 * @param orgName
	 *            其中涉及到的类会自动的加入到from 里面。
	 * @param asName
	 *            这个名字将会用来确定返回值对应的对象属性。
	 * @throws java.lang.Exception
	 */
	public void addAs(String orgName, String asName) throws Exception {
		extractSourceTableToFrom(orgName);
		orgName = ba.convertExpressionToDBForm(orgName);
		String as = orgName + SQLBuilder.AS + asName;
		if (as != null && as.length() > 0 && !asSet.contains(as))
			asSet.add(as);
	}

	/**
	 * 用于单个表达式求值。
	 * 
	 * @param expression
	 * @throws Exception
	 */
	public void setSingleExpression(String expression) throws Exception {
		extractSourceTableToFrom(expression);
		expression = ba.convertExpressionToDBForm(expression);
		if (expression != null && expression.length() > 0
				&& !asSet.contains(expression))
			asSet.add(expression);
	}

	/**
	 * 加入经过 对象=> 关系表 名字转换的from 内容.
	 * 
	 * @param type
	 */
	public void addFrom(Class<?> type) throws Exception {
		String c = ba.analyse(type).getDBName();
		if (c != null && c.length() > 0 && !fromSet.contains(c))
			fromSet.add(c);
	}

	public void addFrom(Class<?>[] lst) throws Exception {
		if (lst == null) { return; }
		for (int i = 0; i < lst.length; i++) {
			addFrom(lst[i]);
		}
	}

	/**
	 * 直接设置条件。<br>
	 * 这个方法会取代掉所有已经存在的条件.
	 * 
	 * @param cnds
	 * @throws java.lang.Exception
	 */
	public void setConditions(String[] cnds) throws Exception {
		if (cnds == null) { return; }
		whereSet.clear();
		and(cnds);
	}

	/**
	 * 设置查询条件 。
	 * 
	 * @param cnds
	 * @throws java.lang.Exception
	 */
	public void and(String[] cnds) throws Exception {
		for (int i = 0; i < cnds.length; i++) {
			and(cnds[i]);
		}
	}

	/**
	 * 设置查询条件。
	 * 
	 * @param cnd
	 * @throws java.lang.Exception
	 */
	public void and(String cnd) throws Exception {
		cnd = cnd.trim();
		extractSourceTableToFrom(cnd);
		if (order.matcher(cnd).matches()) {
			orderBy = ba.convertExpressionToDBForm(cnd);
		}
		else if (group.matcher(cnd).matches()) {
			groupBy = ba.convertExpressionToDBForm(cnd);
		}
		else {
			String c = ba.convertExpressionToDBForm(cnd);
			if (c != null && c.length() > 0 && !whereSet.contains(c))
				whereSet.add(c);
		}

	}

	/**
	 * 设置查询条件。
	 * 
	 * @param value
	 * @param range
	 * @throws java.lang.Exception
	 */
	public void in(String value, String range) throws Exception {
		extractSourceTableToFrom(value);
		extractSourceTableToFrom(range);
		String c = ba.convertExpressionToDBForm(value) + " in ( "
				+ ba.convertExpressionToDBForm(range) + " )";
		if (c != null && c.length() > 0 && !whereSet.contains(c))
			whereSet.add(c);
	}

	/**
	 * 设置查询条件。
	 * 
	 * @param value
	 * @param range
	 * @throws java.lang.Exception
	 */
	public void notIn(String value, String range) throws Exception {
		extractSourceTableToFrom(value);
		extractSourceTableToFrom(range);
		String c = ba.convertExpressionToDBForm(value) + " not in ( "
				+ ba.convertExpressionToDBForm(range) + " )";
		if (c != null && c.length() > 0 && !whereSet.contains(c))
			whereSet.add(c);
	}

	/**
	 * 返回sql 的查插删改语句头。
	 * 
	 * @return
	 */
	@Override
	public String getCmd() {
		if (distinct && type == Query.SELECT) {
			if (orderBy != null && orderBy.length() > 0) {
				String onstr = orderBy.toLowerCase().replace("order by", "").trim();
				if (onstr.startsWith("asc"))
					onstr = onstr.substring(3);
				else if (onstr.endsWith("asc"))
					onstr = onstr.substring(0, onstr.length() - 3);
				if (onstr.startsWith("desc"))
					onstr = onstr.substring(4);
				else if (onstr.endsWith("desc"))
					onstr = onstr.substring(0, onstr.length() - 4);
				return CMD[type] + DISTINCT + "on (" + onstr.trim() + ") ";
			}
			else {
				return CMD[type] + DISTINCT;
			}

		}
		else {
			return CMD[type];
		}

	}

	@Override
	public int getCmdType() {
		return type;
	}

	@Override
	public String getFrom() {
		if (targetRelation != null && targetRelation.length() > 0
				&& !fromSet.contains(targetRelation))
			fromSet.add(this.targetRelation);
		return collectionJoinToString(fromSet, ", ");
	}

	@Override
	public String getFromExceptTarget() {
		if (fromSet.contains(targetRelation))
			fromSet.remove(targetRelation);
		return collectionJoinToString(fromSet, ", ");
	}

	@Override
	public String getConditions() {
		if (whereSet.size() < 1) {
			return null;
		}
		else {
			return collectionJoinToString(whereSet, " and ");
		}
	}

	@Override
	public String getTarget() {
		if (asSet.size() > 0) {
			return collectionJoinToString(asSet, ", ");
		}
		else {
			if (this.type == ObjectQuery.SELECT) {
				return targetRelation + ".*";
			}
			else {
				return targetRelation;
			}

		}
	}

	@Override
	public Class<?> getTargetType() {
		return targetType;
	}

	@Override
	public String getAttributes() {
		if (this.attrSet.size() > 0) {
			return collectionJoinToString(attrSet, ", ");
		}
		else {
			return null;
		}

	}

	@Override
	public boolean isDistinct() {
		return distinct;
	}

	public void setDistinct(boolean distinct) {
		this.distinct = distinct;
	}

	@Override
	public boolean hasAs() {
		if (asSet.size() > 0) {
			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * @return string of paged information.
	 */
	@Override
	public String getPageInfro() {
		if (offset != null && limit != null) { return " offset " + offset
				+ " limit " + limit; }
		return null;
	}

	/**
	 * @param limit
	 *            the pageLength to set
	 */
	public void setLimit(Integer limit) {
		this.limit = limit;
	}

	/**
	 * @param pageNumber
	 *            the pageNumber to set
	 */
	public void setOffset(Integer offset) {
		this.offset = offset;
	}

	/**
	 * @return the orderBy
	 */
	@Override
	public String getOrderBy() {
		return orderBy;
	}

	/**
	 * 从指定字符串中提取表名， 并将之加入fromSet
	 * 
	 * @param orgName
	 * @throws java.lang.Exception
	 */
	private void extractSourceTableToFrom(String orgName) throws Exception {
		List<String> nfrom = NameConvertor.extractBeanName(orgName);
		for (String shortBeanName : nfrom) {
			Class<?> mappedType = ba.getEntityClass(shortBeanName);
			if (mappedType != null) {
				String f = ba.analyse(mappedType).getDBName();
				if (f != null && f.length() > 0 && !fromSet.contains(f))
					fromSet.add(f);
			}
			else {
				throw new NotMappedType(shortBeanName);
			}
		}
	}

	private String collectionJoinToString(Collection<String> set,
			String seperator) {
		if (set.size() < 1) { return ""; }
		Iterator<String> itr = set.iterator();
		StringBuffer buf = new StringBuffer();
		while (itr.hasNext()) {
			buf.append(itr.next());
			buf.append(seperator);
		}
		String frm = buf.toString();
		if (frm.endsWith(seperator)) {
			frm = frm.substring(0, frm.length() - seperator.length());
		}
		return frm;
	}

	@Override
	public String getGroupBy() {
		return groupBy;
	}

}
